/** @file         chrdev_driver.c
 *  @brief        简要说明
 *  @details      详细说明
 *  @author       lzm
 *  @date         2021-02-26 10:17:03
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>


/* 常量 */
#define DEV_NAME "charDev" // 设备名称
#define DEV_CNT  (1)       // 设备数量
#define DEV_INODE_NAME "chrdev" // 设备节点名称

#define BUFF_SIZE 128      // 缓冲区大小


/* 变量 */
static dev_t devNum; // 设备号
static struct cdev chr_dev; // 字符设备结构体
static char cBuf[BUFF_SIZE]; // 缓冲区
struct class *chrdev_class; // 设备类（用于创建各个设备节点）

/* 1. 1.2 实现驱动程序 */

/** @brief  chrdev_open
  * @param 
  * @retval 
  * @author lzm
  */
static int chrdev_open(struct inode *inode, struct file *filp)
{
    printk("chrdev_open!\n");
    return 0;
}

/** @brief  chrdev_release
  * @param 
  * @retval 
  * @author lzm
  */
static int chrdev_release(struct inode *inode, struct file *filp)
{
    printk("chrdev_release!");
    return 0;
}

/** @brief  chrdev_write
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t chrdev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
   unsigned long p = *ppos; // 当前内部文件的读写位置
   int ret;
   int tmp = count ;

   if (p > BUFF_SIZE)
       return 0; // 文件没有缓冲区了

   if (tmp > BUFF_SIZE - p)
      tmp = BUFF_SIZE - p; // 写入到满缓冲区即可

   ret = copy_from_user(cBuf, buf, tmp);
   *ppos += tmp;

   return tmp; // 返回本次写入了多少
}

/** @brief  chrdev_read
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t chrdev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
   unsigned long p = *ppos; // 内部保存，与write中的*ppos是相互独立的
   int ret;
   int tmp = count;

   if (p >= BUFF_SIZE)
       return 0;

   if (tmp > BUFF_SIZE - p)
      tmp = BUFF_SIZE - p;

   ret = copy_to_user(buf, cBuf+p, tmp);
   *ppos +=tmp;

   return tmp;
}


/* 1. 1.1 填充好 file_operation */
static struct file_operations chr_dev_fops = 
{
    .owner   = THIS_MODULE,
    .open    = chrdev_open,
    .release = chrdev_release,
    .write   = chrdev_write,
    .read    = chrdev_read,
};



/* NO.1:Module Init */

/**
 * @brief  char device init
 * @param 
 * @retval 
 * @author lzm
 */
static int __init chrdev_init(void)
{
    int ret = 0;

    printk("chrdev_init\n");

    /* 1. get device num */
    ret = alloc_chrdev_region(&devNum, 0, DEV_CNT, DEV_NAME);
    if(ret < 0)
    {
        printk("chrdev_init error for not get devNum!\n");
        return -1;
    }
    else
    {
        printk("get devNum[%d]\n",devNum);
    }

   
    /* 2. creat device class */
    chrdev_class = class_create(THIS_MODULE, "charDec_class");


    /* 3. init cdev and bind file_operation */
    cdev_init(&chr_dev, &chr_dev_fops);

    /* 4. register cdev for kernel */
    ret = cdev_add(&chr_dev, devNum, DEV_CNT);
    if(ret < 0)
    {
        printk("cdev_add error!\n");
        unregister_chrdev_region(devNum, DEV_CNT); // 添加进kernel失败时，需要归还设备号
        return -1;
    }
    else
    {
        printk("cdev_add successful!\n");
    }

    /* 5. creat device inode */
    device_create(chrdev_class, NULL, MKDEV(MAJOR(devNum), 0), NULL, DEV_INODE_NAME); // /dev/chrdev

    return 1;
}
module_init(chrdev_init);

/* NO.2 Module exit! */

/** @brief  chrdev exit!
  * @param 
  * @retval 
  * @author lzm
  */
static void __exit chrdev_exit(void)
{
    printk("chrdev_exit!\n");
    unregister_chrdev_region(devNum, DEV_CNT); // 归还设备号
    device_destroy(chrdev_class, MKDEV(MAJOR(devNum), 0)); // 删除设备节点
    cdev_del(&chr_dev); // 从kernel中删除字符设备结构体
}
module_exit(chrdev_exit);


/* NO.3 LICENSE and info */
MODULE_AUTHOR("lizhuming");
MODULE_LICENSE("GPL");




